【コピペで使える】リソース削除忘れを防止する自動削除 CloudFormation を作ってみた
こんにちは、森田です。
みなさんは、AWSリソースちゃんと削除できていますか?
私は、特に CloudFormation で構築した場合に、忘れてしまうことがあります。
本記事では、リソース削除忘れを防止する方法として、自動で CloudFormation スタックを削除する方法をご紹介します。
アーキテクチャ
以前、以下のような 時限式CloudFormation をご紹介しましたが、それよりシンプルな構成となっております。
CloudFormationテンプレートの内部に EventBridge Scheduler を埋め込み、特定の時間になったらスタックの削除を行います。
事前準備
なお、事前準備として EventBridge Scheduler で利用する IAMロール を CloudFormation で用意する必要があります。
Description: EventBridge Scheduler IAM Role Resources: Role: Type: 'AWS::IAM::Role' Properties: AssumeRolePolicyDocument: Version: "2012-10-17" Statement: - Effect: "Allow" Principal: Service: - scheduler.amazonaws.com Action: - "sts:AssumeRole" ManagedPolicyArns: - 'arn:aws:iam::aws:policy/PowerUserAccess' Path: "/" Outputs: DeleteSchedulerRoleARN: Value: !GetAtt Role.Arn Export: Name: DeleteSchedulerRoleARN
コピペして使えるテンプレート
あとは、お好きなCloudFormationテンプレートに以下のパラメータとリソースを追加するだけで自動で削除してくれます。
Parameters: Hour: Description: Please type Delete Hour. Type: String Default: "13" Minute: Description: Please type Delete Minute. Type: String Default: "00" Resources: EventSchedule: Type: AWS::Scheduler::Schedule Properties: Description: 'Delete Schedule' ScheduleExpression: !Sub "cron(${Minute} ${Hour} ? * * *)" ScheduleExpressionTimezone: "Asia/Tokyo" FlexibleTimeWindow: Mode: 'OFF' State: ENABLED Target: Arn: "arn:aws:scheduler:::aws-sdk:cloudformation:deleteStack" Input: !Sub "{ \"StackName\": \"${AWS::StackName}\" }" RoleArn: !ImportValue DeleteSchedulerRoleARN
パラメータについては、削除する時間の Hour と Minute を入力します。
- Hour
- 削除する時間の時
- Minute
- 削除する時間の分
実際にやってみる
以下の記事のテンプレートに対して実際に適用させてみます。
なお、EventBridge Scheduler で利用する IAMロールは作成済みとします。
作成していない方は、事前準備を実施してください。
テンプレートの準備
以下のようにテンプレートを用意します。元のテンプレートに削除用のテンプレートを追加します。
AWSTemplateFormatVersion: '2010-09-09' Description: ALB and Nginx on EC2 Parameters: Hour: Description: Please type Delete Hour. Type: String Default: "13" Minute: Description: Please type Delete Minute. Type: String Default: "00" Resources: EventSchedule: Type: AWS::Scheduler::Schedule Properties: Description: 'Delete Schedule' ScheduleExpression: !Sub "cron(${Minute} ${Hour} ? * * *)" ScheduleExpressionTimezone: "Asia/Tokyo" FlexibleTimeWindow: Mode: 'OFF' State: ENABLED Target: Arn: "arn:aws:scheduler:::aws-sdk:cloudformation:deleteStack" Input: !Sub "{ \"StackName\": \"${AWS::StackName}\" }" RoleArn: !ImportValue DeleteSchedulerRoleARN VPC: Type: AWS::EC2::VPC Properties: CidrBlock: 10.0.0.0/16 Tags: - Key: Name Value: !Sub ${AWS::StackName}-vpc PublicSubnet0: Type: AWS::EC2::Subnet DependsOn: AttachGateway Properties: VpcId: !Ref VPC AvailabilityZone: ap-northeast-1a CidrBlock: 10.0.0.0/24 Tags: - Key: Name Value: !Sub ${AWS::StackName}-public-1a-subnet PublicSubnet1: Type: AWS::EC2::Subnet DependsOn: AttachGateway Properties: VpcId: !Ref VPC AvailabilityZone: ap-northeast-1c CidrBlock: 10.0.1.0/24 Tags: - Key: Name Value: !Sub ${AWS::StackName}-public-1c-subnet InternetGateway: Type: AWS::EC2::InternetGateway Properties: Tags: - Key: Name Value: !Sub ${AWS::StackName}-igw AttachGateway: Type: AWS::EC2::VPCGatewayAttachment Properties: VpcId: !Ref VPC InternetGatewayId: !Ref InternetGateway PublicRouteTable: Type: AWS::EC2::RouteTable DependsOn: AttachGateway Properties: VpcId: !Ref VPC Tags: - Key: Name Value: !Sub ${AWS::StackName}-public-rt PublicRoute: Type: AWS::EC2::Route DependsOn: AttachGateway Properties: RouteTableId: !Ref PublicRouteTable DestinationCidrBlock: 0.0.0.0/0 GatewayId: !Ref InternetGateway PublicSubnet0RouteTableAssociation: Type: AWS::EC2::SubnetRouteTableAssociation Properties: SubnetId: !Ref PublicSubnet0 RouteTableId: !Ref PublicRouteTable PublicSubnet1RouteTableAssociation: Type: AWS::EC2::SubnetRouteTableAssociation Properties: SubnetId: !Ref PublicSubnet1 RouteTableId: !Ref PublicRouteTable ALBSecurityGroup: Type: "AWS::EC2::SecurityGroup" Properties: GroupDescription: "ALB SG" GroupName: !Sub ${AWS::StackName}-alb-sg VpcId: !Ref VPC Tags: - Key: Name Value: !Sub ${AWS::StackName}-alb-sg SecurityGroupIngress: - IpProtocol: tcp FromPort: 80 ToPort: 80 CidrIp: 0.0.0.0/0 - IpProtocol: tcp FromPort: 443 ToPort: 443 CidrIp: 0.0.0.0/0 EC2SecurityGroup: Type: "AWS::EC2::SecurityGroup" Properties: GroupDescription: "EC2 SG" GroupName: !Sub ${AWS::StackName}-ec2-sg VpcId: !Ref VPC Tags: - Key: Name Value: !Sub ${AWS::StackName}-ec2-sg SecurityGroupIngress: - IpProtocol: tcp FromPort: 80 ToPort: 80 SourceSecurityGroupId: !Ref ALBSecurityGroup EC2InstanceProfile: Type: AWS::IAM::InstanceProfile Properties: Path: "/" Roles: - !Ref EC2Role InstanceProfileName: !Sub ${AWS::StackName}-ec2-profile EC2Role: Type: "AWS::IAM::Role" Properties: Path: "/" RoleName: !Sub ${AWS::StackName}-ec2-role Tags: - Key: Name Value: !Sub ${AWS::StackName}-ec2-role AssumeRolePolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Principal: Service: ec2.amazonaws.com Action: sts:AssumeRole ManagedPolicyArns: - "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore" KeyPair: Type: 'AWS::EC2::KeyPair' Properties: KeyName: !Sub ${AWS::StackName}-keypair WebServer: Type: AWS::EC2::Instance Properties: Tags: - Key: Name Value: !Sub ${AWS::StackName}-web InstanceType: t3.nano BlockDeviceMappings: - DeviceName: /dev/sda1 Ebs: VolumeType: gp3 VolumeSize: 8 DeleteOnTermination: true Encrypted: true NetworkInterfaces: - AssociatePublicIpAddress: "true" DeviceIndex: "0" GroupSet: - !Ref EC2SecurityGroup SubnetId: !Ref PublicSubnet0 ImageId: '{{resolve:ssm:/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2}}' IamInstanceProfile: !Ref EC2InstanceProfile KeyName: !Ref KeyPair DisableApiTermination: false EbsOptimized: true UserData: Fn::Base64: | #!/bin/bash sudo amazon-linux-extras install nginx1 sudo systemctl enable nginx sudo systemctl start nginx ALB: Type: AWS::ElasticLoadBalancingV2::LoadBalancer Properties: Type: "application" Scheme: "internet-facing" Name: !Sub ${AWS::StackName}-alb Tags: - Key: Name Value: !Sub ${AWS::StackName}-alb IpAddressType: ipv4 Subnets: - !Ref PublicSubnet0 - !Ref PublicSubnet1 SecurityGroups: - !Ref ALBSecurityGroup ListenerHTTP: Type: AWS::ElasticLoadBalancingV2::Listener Properties: DefaultActions: - Type: forward TargetGroupArn: !Ref TargetGroup LoadBalancerArn: !Ref ALB Port: 80 Protocol: HTTP TargetGroup: Type: AWS::ElasticLoadBalancingV2::TargetGroup Properties: Name: !Sub ${AWS::StackName}-tg Tags: - Key: Name Value: !Sub ${AWS::StackName}-tg Port: 80 Protocol: HTTP Matcher: HttpCode: '200' VpcId: !Ref VPC TargetType: instance Targets: - Id: !Ref WebServer Outputs: ALBURL: Description: ALB endpoint URL Value: !Join - "" - - http:// - !GetAtt ALB.DNSName
スタックの作成
あとは、上記のテンプレートでスタックを作成します。以下の場合は、14:00に削除するようにパラメータを与えています。
スタック作成後は、しばらく待つと通常通り CREATE_COMPLETE になります。
スタックの自動削除
パラメータに指定した時間までしばらく待ちます。
すると、以下のようにスタックが自動的に削除されます。
まとめ
本記事では、自動でCloudFormationのスタックを削除する方法をご紹介しました。
EventBridge Scheduler のおかげで結構シンプルに実現できたと思います。
皆さんもリソースの削除忘れがないようこのテンプレートをぜひ利用してみてください。